﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace AdvanceLib.Core
{
    public class FreeSpaceFinder
    {
        #region Constructors
        public FreeSpaceFinder(Stream stream) : this(stream, 0xFF) { }
        public FreeSpaceFinder(Stream stream, byte marker)
        {
            this._reader = new BinaryReader(stream);
            this._watch = new Stopwatch();
            this._reservedOffsets = new List<ReservedOffset>();

            this.FreeSpaceMarker = marker;
        }
        #endregion

        #region Destructors
        ~FreeSpaceFinder()
        {
        }
        #endregion

        #region Fields
        private BinaryReader _reader;
        private int _bufferSize = 1024 * 8;
        private Stopwatch _watch;

        private List<ReservedOffset> _reservedOffsets;
        #endregion

        #region Properties
        public byte FreeSpaceMarker { get; set; }
        public long InitialOffset { get; set; }
        public long ElapsedTicks { get; set; }
        #endregion

        #region Methods
        public long Search(long length)
        {
            this._watch.Reset();
            this._watch.Start();

            long offset = -1, currentLength = 0;
            int dataPasses = 0;
            byte[] data = new byte[this._bufferSize];

            this._reader.BaseStream.Position = this.InitialOffset;

            while (this._reader.BaseStream.CanRead)
            {
                data = this._reader.ReadBytes(this._bufferSize);

                for (int i = 0; i < data.Length; i++)
                {
                    if (currentLength >= length + 1)
                    {
                        if (!this.IsOffsetReserved(offset, currentLength))
                        {
                            this._reservedOffsets.Add(new ReservedOffset()
                            {
                                Offset = offset + 1,
                                Length = currentLength
                            });

                            this._watch.Stop();
                            this.ElapsedTicks = this._watch.ElapsedTicks;

                            return offset + 1;
                        }
                        else
                        { this.Reset(ref offset, ref currentLength); }
                    }
                    if (data[i] == this.FreeSpaceMarker &&
                        offset == -1) 
                    { 
                        offset = this.InitialOffset + dataPasses * this._bufferSize + i;
                    }
                    else if (data[i] == this.FreeSpaceMarker) { currentLength++; }
                    else
                    {
                        this.Reset(ref offset, ref currentLength);
                    }
                }

                dataPasses++;
            }

            this._watch.Stop();
            this.ElapsedTicks = this._watch.ElapsedTicks;

            return offset;
        }
        private void Reset(ref long offset, ref long currentLength)
        {
            offset = -1;
            currentLength = 0;
        }
        private bool IsOffsetReserved(long offset, long bytes)
        {
            return this._reservedOffsets.Count(reserved => reserved.Offset < offset + bytes && reserved.Offset + reserved.Length > offset) > 0;
        }
        #endregion
    }
}
